#ifndef _MP4U_H
#define _MP4U_H

#include <stdio.h> 
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>  
#include <stdint.h>  
#include <string.h>  
#include <unistd.h>
#include <inttypes.h>  
#include <errno.h>
  
#define BUFFERSIZE  512*1024
#define MAX_BUFFERSIZE 1024*1024
#define MP4U_MAX_OFF_T_VALUE  9223372036854775807LL
#define  _FILE_OFFSET_BITS 64
#define  MP4U_OK          0
#define  MP4U_ERROR      -1
#define  MP4U_AGAIN      -2
#define  MP4U_BUSY       -3
#define  MP4U_DONE       -4
#define  MP4U_DECLINED   -5
#define  MP4U_ABORT      -6

#define MP4_TRAK_ATOM     0
#define MP4_TKHD_ATOM     1
#define MP4_MDIA_ATOM     2
#define MP4_MDHD_ATOM     3
#define MP4_HDLR_ATOM     4
#define MP4_MINF_ATOM     5
#define MP4_VMHD_ATOM     6
#define MP4_SMHD_ATOM     7
#define MP4_DINF_ATOM     8
#define MP4_STBL_ATOM     9
#define MP4_STSD_ATOM    10
#define MP4_STTS_ATOM    11
#define MP4_STTS_DATA    12
#define MP4_STSS_ATOM    13
#define MP4_STSS_DATA    14
#define MP4_CTTS_ATOM    15
#define MP4_CTTS_DATA    16
#define MP4_STSC_ATOM    17
#define MP4_STSC_CHUNK   18
#define MP4_STSC_DATA    19
#define MP4_STSZ_ATOM    20
#define MP4_STSZ_DATA    21
#define MP4_STCO_ATOM    22
#define MP4_STCO_DATA    23
#define MP4_CO64_ATOM    24
#define MP4_CO64_DATA    25

#define MP4_LAST_ATOM    MP4_CO64_DATA


#define mp4u_mp4_get_16value(p)                                                 \
    ( ((uint16_t) ((u_char *) (p))[0] << 8)                                     \
    + (           ((u_char *) (p))[1]) )

#define mp4u_mp4_set_16value(p, n)                                              \
    ((u_char *) (p))[0] = (u_char) ((n) >> 8);                                  \
    ((u_char *) (p))[1] = (u_char)  (n)

#define mp4u_mp4_get_24value(p)                                                 \
    ( ((uint32_t) ((u_char *) (p))[0] << 16)                                    \
    + (           ((u_char *) (p))[1] << 8)                                     \
    + (           ((u_char *) (p))[2]) )

#define mp4u_mp4_set_24value(p, n)                                              \
    ((u_char *) (p))[0] = (u_char) ((n) >> 16);                                 \
    ((u_char *) (p))[1] = (u_char) ((n) >> 8);                                  \
    ((u_char *) (p))[2] = (u_char)  (n)


#define mp4u_mp4_get_32value(p)                                                 \
    ( ((uint32_t) ((u_char *) (p))[0] << 24)                                    \
    + (           ((u_char *) (p))[1] << 16)                                    \
    + (           ((u_char *) (p))[2] << 8)                                     \
    + (           ((u_char *) (p))[3]) )

#define mp4u_mp4_set_32value(p, n)                                              \
    ((u_char *) (p))[0] = (u_char) ((n) >> 24);                                 \
    ((u_char *) (p))[1] = (u_char) ((n) >> 16);                                 \
    ((u_char *) (p))[2] = (u_char) ((n) >> 8);                                  \
    ((u_char *) (p))[3] = (u_char)  (n)

#define mp4u_mp4_get_64value(p)                                                 \
    ( ((uint64_t) ((u_char *) (p))[0] << 56)                                    \
    + ((uint64_t) ((u_char *) (p))[1] << 48)                                    \
    + ((uint64_t) ((u_char *) (p))[2] << 40)                                    \
    + ((uint64_t) ((u_char *) (p))[3] << 32)                                    \
    + ((uint64_t) ((u_char *) (p))[4] << 24)                                    \
    + (           ((u_char *) (p))[5] << 16)                                    \
    + (           ((u_char *) (p))[6] << 8)                                     \
    + (           ((u_char *) (p))[7]) )

#define mp4u_mp4_set_64value(p, n)                                              \
    ((u_char *) (p))[0] = (u_char) ((uint64_t) (n) >> 56);                      \
    ((u_char *) (p))[1] = (u_char) ((uint64_t) (n) >> 48);                      \
    ((u_char *) (p))[2] = (u_char) ((uint64_t) (n) >> 40);                      \
    ((u_char *) (p))[3] = (u_char) ((uint64_t) (n) >> 32);                      \
    ((u_char *) (p))[4] = (u_char) (           (n) >> 24);                      \
    ((u_char *) (p))[5] = (u_char) (           (n) >> 16);                      \
    ((u_char *) (p))[6] = (u_char) (           (n) >> 8);                       \
    ((u_char *) (p))[7] = (u_char)             (n)

#define mp4u_mp4_is_video_trak(t)                                               \
    (t)->vmhd_atom_buf.pos != NULL

#define mp4u_mp4_stco_is64(t)                                                   \
    (t)->co64_atom_buf.pos != NULL

#define mp4u_log(...)                                           \
        printf(__VA_ARGS__);                                    \
        printf("\n");

#define mp4u_log_error  mp4u_log 

typedef struct mp4u_file_s              mp4u_file_t;
typedef struct file_s                   file_t; 
typedef struct mp4u_array_s             mp4u_array_t;
typedef struct mp4u_trak_s              mp4u_trak_t;
typedef struct mp4u_buf_s               mp4u_buf_t;
typedef struct mp4u_spspps_s            mp4u_spspps_t;
typedef struct mp4u_mp4_tkhd_atom_s     mp4u_mp4_tkhd_atom_t;
typedef struct mp4u_mp4_tkhd64_atom_s   mp4u_mp4_tkhd64_atom_t;
typedef struct mp4u_mp4_avc1_atom_s     mp4u_mp4_avc1_atom_t;
typedef struct mp4u_chain_s             mp4u_chain_t; 
typedef struct mp4u_mp4_mp4a_atom_s     mp4u_mp4_mp4a_atom_t;

struct mp4u_array_s {
    void        *elts;
    uint32_t     nelts;
    size_t       size;
    uint32_t     nalloc;
};

struct file_s {
    int         fd;
    char        *name;

    off_t       offset;
    off_t       sys_offset;
    uint32_t    size;
};

struct mp4u_buf_s {
    u_char      *pos;
    u_char      *last;
    off_t       file_pos;
    off_t       file_last;

    u_char      *start;         /* start of buffer */
    u_char      *end;           /* end of buffer */
    file_t      *file;

    unsigned    in_file:1;
};

struct mp4u_chain_s {
    mp4u_buf_t    *buf;
    mp4u_chain_t  *next;
};


struct mp4u_spspps_s {
    unsigned  version:8;
    unsigned  profile:8;
    unsigned  comprofiles:8;
    unsigned  level:8;
    unsigned  naullen:8;
    unsigned  spscount:8;
    unsigned  spslen:16;
    u_char    *sps;
    unsigned  ppscount:8;
    unsigned  ppslen:16;
    u_char    *pps;
};

typedef struct {
    u_char    count[4];
    u_char    duration[4];
} mp4u_mp4_stts_entry_t;

typedef struct {
    u_char      chunk[4];
    u_char      samples[4];
    u_char      id[4];
} mp4u_mp4_stsc_entry_t;

struct mp4u_trak_s {
    uint32_t              timescale;
    uint32_t              time_to_sample_entries;
    uint32_t              sample_to_chunk_entries;
    uint32_t              sync_samples_entries;
    uint32_t              composition_offset_entries;
    uint32_t              sample_sizes_entries;
    uint32_t              chunks;

    uint32_t              start_sample;
    uint32_t              start_chunk;
    uint32_t              chunk_samples;
    uint64_t              chunk_samples_size;
    off_t                 start_offset;

    size_t                tkhd_size;
    size_t                mdhd_size;
    size_t                hdlr_size;
    size_t                vmhd_size;
    size_t                smhd_size;
    size_t                dinf_size;

    size_t                avcC_size;
    mp4u_spspps_t         spspps;
    size_t                mp4a_size;
    size_t                size;

    mp4u_chain_t          out[MP4_LAST_ATOM + 1];

    mp4u_buf_t             trak_atom_buf;
    mp4u_buf_t             tkhd_atom_buf;
    mp4u_buf_t             mdia_atom_buf;
    mp4u_buf_t             mdhd_atom_buf;
    mp4u_buf_t             hdlr_atom_buf;
    mp4u_buf_t             minf_atom_buf;
    mp4u_buf_t             vmhd_atom_buf;
    mp4u_buf_t             smhd_atom_buf;
    mp4u_buf_t             dinf_atom_buf;
    mp4u_buf_t             stbl_atom_buf;
    mp4u_buf_t             stsd_atom_buf;
    mp4u_buf_t             stts_atom_buf;
    mp4u_buf_t             stts_data_buf;
    mp4u_buf_t             stss_atom_buf;
    mp4u_buf_t             stss_data_buf;
    mp4u_buf_t             ctts_atom_buf;
    mp4u_buf_t             ctts_data_buf;
    mp4u_buf_t             stsc_atom_buf;
    mp4u_buf_t             stsc_chunk_buf;
    mp4u_buf_t             stsc_data_buf;
    mp4u_buf_t             stsz_atom_buf;
    mp4u_buf_t             stsz_data_buf;
    mp4u_buf_t             stco_atom_buf;
    mp4u_buf_t             stco_data_buf;
    mp4u_buf_t             co64_atom_buf;
    mp4u_buf_t             co64_data_buf;

    mp4u_buf_t             avc1_atom_buf;
    mp4u_buf_t             avcC_data_buf;
    mp4u_buf_t             mp4a_atom_buf;
    mp4u_buf_t             esds_data_buf;

    mp4u_mp4_stsc_entry_t  stsc_chunk_entry;
};

/*typedef struct mp4u_mp4_chunk_chains_s {
    uint32_t                trak_idx;
    mp4u_mp4_stsc_entry_t  *prev;
    mp4u_mp4_stsc_entry_t  *next;
    mp4u_mp4_stsc_entry_t  *stsc_chunk_entry;
} mp4u_mp4_chunk_chains_t;*/

struct mp4u_file_s {
    file_t                file;

    u_char               *buffer;
    u_char               *buffer_start;
    u_char               *buffer_pos;
    u_char               *buffer_end;
    size_t                buffer_size;

    off_t                 offset;
    off_t                 end;
    off_t                 content_length;
    uint32_t              start;
    uint32_t              timescale;
    mp4u_array_t          trak;
    mp4u_trak_t           traks[2];

    size_t                ftyp_size;
    size_t                moov_size;

    mp4u_chain_t          *out;
    mp4u_chain_t           ftyp_atom;
    mp4u_chain_t           moov_atom;
    mp4u_chain_t           mvhd_atom;
    mp4u_chain_t           mdat_atom;
    mp4u_chain_t           mdat_data;

    mp4u_buf_t             ftyp_atom_buf;
    mp4u_buf_t             moov_atom_buf;
    mp4u_buf_t             mvhd_atom_buf;
    mp4u_buf_t             mdat_atom_buf;
    mp4u_buf_t             mdat_data_buf;

    u_char                 moov_atom_header[8];
    u_char                 mdat_atom_header[16];
};

struct  mp4u_mp4_mp4a_atom_s {
    u_char    size[4];
    u_char    name[4];
    u_char    reserved[6];
    u_char    idx[2];
    u_char    version[2];
    u_char    revision[2];
    u_char    vendor[4];
    u_char    channels[2];
    u_char    samplesize[2];
    u_char    compressionid[2];
    u_char    packetsize[2];
    u_char    samplerate[4];
};

struct mp4u_mp4_avc1_atom_s {
    u_char    size[4];
    u_char    name[4];
    u_char    reserved[6];
    u_char    idx[2];
    u_char    version[2];
    u_char    revision[2];
    u_char    vendor[4];
    u_char    temporalqulity[4];
    u_char    spatialqulity[4];
    u_char    framepixel[4];
    u_char    resolution[8];
    u_char    datasize[4];
    u_char    framecount[2];
    u_char    encnamelength[1];
    u_char    encname[31];
    u_char    pixeldepth[2];
    u_char    colortableid[2];
};

struct mp4u_mp4_tkhd_atom_s {
    u_char    size[4];
    u_char    name[4];
    u_char    version[1];
    u_char    flags[3];
    u_char    creation_time[4];
    u_char    modification_time[4];
    u_char    track_id[4];
    u_char    reserved1[4];
    u_char    duration[4];
    u_char    reserved2[8];
    u_char    layer[2];
    u_char    group[2];
    u_char    volume[2];
    u_char    reverved3[2];
    u_char    matrix[36];
    u_char    width[4];
    u_char    heigth[4];
};

struct mp4u_mp4_tkhd64_atom_s {
    u_char    size[4];
    u_char    name[4];
    u_char    version[1];
    u_char    flags[3];
    u_char    creation_time[8];
    u_char    modification_time[8];
    u_char    track_id[4];
    u_char    reserved1[4];
    u_char    duration[8];
    u_char    reserved2[8];
    u_char    layer[2];
    u_char    group[2];
    u_char    volume[2];
    u_char    reverved3[2];
    u_char    matrix[36];
    u_char    width[4];
    u_char    heigth[4];
};




void*    mp4u_array_push(mp4u_array_t *a);
int      mp4u_init(mp4u_file_t *mp4, const char *file);
void     mp4u_free(mp4u_file_t *mp4);
int      mp4u_process(mp4u_file_t *mp4);
ssize_t  mp4u_init_file(file_t *file, const char *filename);
ssize_t  mp4u_read_file(file_t *file, u_char *buf, size_t size, off_t offset);

#endif //_MP4U_H
